from collections import deque
from .builder import Builder


class Node(object):
    _registry = {}
    CACHE_DIRECTORY = ".gabotcache"
    
    def __init__(self, filename, builder):
        self.filename = filename
        self.dirname = os.path.dirname(filename)
        self.relname = os.path.basename(filename)
        parts = self.relname.split(".", 1)
        if len(parts) == 2:
            self.basename, self.fullext = parts
            self.ext = self.fullext.rsplit(".", 1)[-1]
        else:
            self.basename = parts[0]
            self.ext = self.fullext = None
        self.builder = builder
        self.params = None
        self.output = None
        self.cachedir = os.path.join(self.dirname, self.CACHE_DIRECTORY)
        self.cachefile = None
        self.scanned = False
        self.rebuild = False
        self.children = set()
    
    @classmethod
    def new(cls, filename, searchpath = None, builder = None):
        if searchpath:
            for sp in searchpath:
                canonized = _canonize(os.path.join(sp, filename))
                if os.path.exists(canonized):
                    break
            else:
                raise FileNotFound(filename)
        else:
            canonized = _canonize(filename)
        if canonized in cls._registry:
            return cls._registry[canonized]
        if builder is None:
            ext = os.path.basename(canonized).split(".", 1)[-1]
            builder = Builder.select(ext)
        self = cls._registry[canonized] = cls(canonized, builder)
        return self
    
    def new_child(self, filename, searchpath = None, builder = None):
        child = self.new(filename, searchpath = searchpath, builder = builder)
        self.children.add(child)
        return child
    
    def scan(self):
        self.scanned = True
        os.chdir(self.dirname)
        self.builder.scan(self)
        self.output = self.builder.get_output(self)
        if self.output is not None:
            self.output = _canonize(self.output)
            sig = self.builder.get_signature(self)
            self.cachefile = "%s--%s" % (self.relname, sig)
    
    def build(self):
        os.chdir(self.dirname)
        return self.builder.build(self)
    
    def clean(self):
        if self.output is None:
            return
        if os.path.exists(self.output):
            os.remove(self.output)
        if os.path.isdir(self.cachedir):
            return
        for fn in os.listdir(self.cachedir):
            if fn.startswith(self.relname + "--"):
                os.remove(fn)
    
    def validate_cache(self):
        """returns True if the cache is valid, False if needs rebuild"""
        if self.output is None or self.cachefile is None:
            return True
        if os.path.isfile(os.path.join(self.cachedir, self.cachefile)):
            return True
        else:
            return False
    
    def store_cache(self):
        if self.output is None:
            return
        if not os.path.isdir(self.cachedir):
            os.mkdir(self.cachedir)
        fn = os.path.join(self.cachedir, self.cachefile)
        shutil.copyfile(self.output, fn)
    
    def retrieve_cache(self):
        if self.output is None:
            return
        fn = os.path.join(self.cachedir, self.cachefile)
        if not os.path.isfile(fn):
            raise CacheError("cached file does not exist")
        shutil.copyfile(fn, self.output)


def _canonize(filename):
    return os.path.normpath(os.path.abspath(os.path.expandvars(
        os.path.expanduser(filename))))

def depth_sort(rootnode):
    _depth_sort(rootnode, levels, {})
    return [depths[i] for i in range(len(levels))]


def _depth_sort(node, levels, depths):
    deepest = 0
    depths[node] = 0
    for child in node.children:
        d = depths.get(child)
        if d is None:
            d = _depth_sort(child, levels, depths)
        d += 1
        if d > deepest:
            deepest = d
    depths[node] = deepest
    level = levels.get(deepest)
    if level is None:
        level = levels[deepest] = set()
    level.add(node)
    return deepest


def mark_changed(nodelist):
    changed = set()
    for level in nodelist:
        for node in level:
            node.rebuild = False
            for child in node.children:
                if child in changed:
                    node.rebuild = True
                    break
            if node.rebuild or not node.validate_cache():
                node.rebuild = True
                changed.add(node)
                node.clean()
    return len(changed)


def scan_all(rootnode):
    queue = deque([rootnode])
    while queue:
        node = queue.popleft()
        if not node.scanned:
            node.scan()
            queue.extend(node.children)


class Namespace(object):
    __slots__ = ["parent", "locals", "__weakref__"]
    def __init__(self, parent = None):
        object.__setattr__(self, "parent", parent)
        object.__setattr__(self, "locals", {})
    
    def __dir__(self):
        this = self
        attrs = set()
        while this is not None:
            attrs.update(object.__getattribute__(this, "locals").keys())
            this = object.__getattribute__(this, "parent")
        return attrs
    
    def __iter__(self):
        for name in Namespace.__dir__(self):
            yield name, self[name]
    def __contains__(self, name):
        return self[name] is not None
    
    def __delitem__(self, name):
        self[name] = None
    def __getitem__(self, name):
        this = self
        while this is not None:
            item = object.__getattribute__(this, "locals").get(name)
            if item is not None:
                return item
            this = object.__getattribute__(this, "parent")
        return None
    def __setitem__(self, name, value):
        object.__getattribute__(self, "locals")[name] = value
    
    __delattr__ = __delitem__
    __getattribute__ = __getitem__
    __setattr__ = __setitem__



























